package com.songyang.yougnsql.core.data.page;

import com.songyang.yougnsql.common.ErrorMsg;
import com.songyang.yougnsql.common.YoungSQLException;
import com.songyang.yougnsql.core.utils.ByteUtils;

import java.util.Arrays;

/**
 * @ClassName PageX
 * @Description 普通页面
 * 普通页面保留着2字节的偏移，后面是数据，2字节的偏移记录着当前的最新偏移量
 * [offset][data1][data2]... offset 2字节的偏移量 data 就是数据对象
 * @date 2022/12/5 13:38
 * @Author yanceysong
 * @Version 1.0
 */
public class PageX {
    /**
     * 空白字节的起始偏移量
     */
    private static final short OF_FREE = 0;
    /**
     * 空白字节的长度
     */
    private static final short LEN_FREE = 2;
    /**
     * 页面的最大空闲空间
     */
    public static final int MAX_FREE_SPACE = Page.PAGE_SIZE - LEN_FREE - OF_FREE;

    public static byte[] initRaw() {
        byte[] raw = new byte[Page.PAGE_SIZE];
        setOffset(raw, LEN_FREE);
        return raw;
    }

    /**
     * 设置普通页的当前空白数据的起始偏移量
     *
     * @param raw        普通页数据
     * @param offsetData 空白数据的起始偏移量
     */
    private static void setOffset(byte[] raw, short offsetData) {
        System.arraycopy(ByteUtils.short2Byte(offsetData), 0, raw, OF_FREE, LEN_FREE);
    }

    /**
     * 返回空白偏移量内的数据
     *
     * @param raw 一页的数据
     * @return 当前页面的空白位置偏移量
     */
    private static short getOffset(byte[] raw) {
        return ByteUtils.parseShort(Arrays.copyOfRange(raw, OF_FREE, LEN_FREE));
    }

    /**
     * 返回空白偏移量内的数据
     *
     * @param page 普通页
     * @return 数据插入的位置
     */
    public static short getOffset(Page page) {
        page.setDirty(true);
        return getOffset(page.getData());
    }

    /**
     * 向普通页插入数据
     *
     * @param page 普通页面
     * @param raw  要插入的数据
     * @return 返回插入的起始位置
     */
    public static short insert(Page page, byte[] raw) {
        page.setDirty(true);
        //这个偏移量记录着当前要插入的位置
        short offset = getOffset(page);
        //写入数据
        System.arraycopy(raw, 0, page.getData(), offset, raw.length);
        //更新空白位置的偏移量
        setOffset(page.getData(), (short) (offset + raw.length));
        return offset;
    }

    /**
     * 强制向某一个偏移量位置插入一条数据,该操作很可能会覆盖掉其他数据
     * 所以使用起来要格外小心！！
     *
     * @param page   普通页面
     * @param raw    要插入的数据
     * @param offset 要插入的起始位置
     * @throws YoungSQLException 如果当前在某个位置要插入的数据太大，就会抛出这个异常
     */
    public static void insert(Page page, byte[] raw, short offset) throws YoungSQLException {
        page.setDirty(true);
        //如果当前页面不足以满足在固定位置插入该大小的数据
        if (offset + raw.length > PageX.MAX_FREE_SPACE) {
            throw new YoungSQLException(ErrorMsg.DATA_TOO_LARGE_EXCEPTION);
        }
        //写入数据
        System.arraycopy(raw, 0, page.getData(), offset, raw.length);
        //更新偏移量
        short nowOffset = getOffset(page.getData());
        //如果当前的偏移量比插入后应该有的偏移量小，则更新较大的偏移量
        if (nowOffset < offset + raw.length) {
            setOffset(raw, (short) (offset + raw.length));
        }
    }

    /**
     * 在特定偏移量下更新数据，需要使用者自己判断更新的数据不会覆盖其他数据
     *
     * @param page   普通页
     * @param raw    要更新的数据
     * @param offset 更新的偏移量
     */
    public static void update(Page page, byte[] raw, short offset) {
        page.setDirty(true);
        System.arraycopy(raw, 0, page.getData(), offset, raw.length);
    }

    /**
     * 获取普通页面的剩余空间大小
     *
     * @param page 普通页面
     * @return 普通页面的剩余空间大小
     */
    public static int getFreeSpace(Page page) {
        return Page.PAGE_SIZE - getOffset(page.getData());
    }
}
